Repository navigation
fix: show loading and unavailable states for operator balance and fee - #1876
Merged
Merged
Conversation
The operator detail page rendered a confident "0 ETH" for Balance and Annual Fee whenever the on-chain reads were pending or failing, because useOperatorEarningsAndFees coalesced `data ?? 0n`. - expose earningsStatus / feesStatus from the hook via combineQueryStatus - treat fetchStatus "paused" as unavailable: a read that cannot reach the RPC parks at status "pending" and never resolves to an error, which would otherwise leave the UI on a skeleton indefinitely - render a skeleton while pending and an explicit unavailable message instead of a zero amount - gate Withdraw and Update Fee on query success rather than on the coalesced bigint, so a failed read no longer leaves Update Fee clickable into the fee wizard
nir-ssvlabs
approved these changes
Sep 1, 2026
sebastian-ssvlabs
approved these changes
Sep 1, 2026
|
Approved we can merge to main @Chris-ssvlabs @sebastian-ssvlabs |
Chris-ssvlabs
added a commit
that referenced
this pull request
Sep 2, 2026
…ates-main fix: show loading and unavailable states for operator balance and fee (#1876)
Open
9 of 12 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The operator detail page renders a confident
0 ETHfor Balance and Annual Fee whenever the on-chain reads are pending or failing — indistinguishable from a genuine on-chain zero.Reported against mainnet operator 423, where the values are demonstrably non-zero on-chain:
The page looks half-loaded because the header (Name, Status, Validators, 30D, Total ETH Managed) and the Validators table come from the REST API, which needs no wallet or RPC. Balance and Annual Fee are the only values read on-chain. The two sources fail independently, so the page renders complete but zeroed.
Root cause
useOperatorEarningsAndFeescollapsed three distinct query states into one value:The hook returned only the derived bigints, and
operator.tsxnever readisLoading/isError. All four reads share one batched JSON-RPC POST, so they fail together — which is also why the SSV rows vanish (both are gated onyearlyFeeSSV).Changes
earningsStatus/feesStatusfrom the hook via the existingcombineQueryStatus.fetchStatus: "paused"as unavailable. A read that cannot reach the RPC parks atstatus: "pending"/fetchStatus: "paused"and never resolves to an error, so anisError-only fallback would leave the UI on a skeleton indefinitely.WithdrawandUpdate Feeon query success rather than on the coalesced bigint. Previously a failed fee read leftUpdate Feeenabled (dataisundefined, not0n), routing into the fee wizard with no fee loaded.OperatorTableRow, the hook's other consumer, uses only the coalesced amounts and is unaffected.Testing
Reproduced by pointing
localStorage.customRpcUrl_1at a dead endpoint and reloading/operators/423— pixel-identical to the report: header + 29 validators fine, both cards0 ETH / $0, no SSV rows, nothing logged to console.All three states verified in the browser after the fix:
0.0228 ETH/0.0028 ETH+1.2527 SSV, buttons enabledGates:
git diff --checkclean,tsc -bclean, eslint clean, prettier clean, 119 tests pass.Open question (not addressed here)
Why the reads pause at all.
readContractQueryOptionsreturns onlyqueryFn/queryKey, so wagmi is not overriding thenetworkMode: "always"set on these queries — yet they still park atpausedunderPersistQueryClientProviderwhilenavigator.onLineistrue. Instrumented evidence:This likely affects every
useReadContractin the app, not just this page. I left it alone because the plausible fix is a QueryClient-level default with app-wide blast radius. I originally added a "Try again" button here and removed it after verifying it was inert in this state —refetch()does not resume a paused query, and the state was byte-identical before and after a click.Note for reviewers
Two pre-existing issues surfaced while working on this, both left untouched:
yearlyFeeSSV(a fee value) rather thanbalanceSSV— looks wrong, but changing it alters what gets displayed.pnpm test:runfails 5 suites locally withVITE_SSV_NETWORKS is not defined— vitest runs in modetestand never loads.env.development.local. With the var supplied, 119 tests pass instead of 73, so 46 tests are silently skipped for anyone without it.